﻿using AiMoBridge.Interface;
using System.Drawing;
using System.IO;
using System.IO.Pipes;
using System.Text;

namespace AiMoWindows.Entrance.Connect
{
    class PipeClient : IDisposable
    {
        StreamWriter Writer { get; }
        StreamReader Reader { get; }
        NamedPipeClientStream Client { get; }
        ReaderWriterLock ReaderWriterLock { get; } = new ReaderWriterLock();
        bool m_OnRunning = false;
        public PipeClient(string serverPipeName)
        {
            Client = new NamedPipeClientStream(".", serverPipeName, PipeDirection.InOut);
            Reader = new StreamReader(Client);
            Writer = new StreamWriter(Client);
            Client.Connect();
        }
        public void Init(out string currentRhinoVersion, out string[] rhinoAssemblyLocations)
        {
            string feedback = SendAndReceive("Connected") ?? throw new Exception("Cannot Init PipeClient for receiving null string");
            string[] texts = Split(feedback);
            currentRhinoVersion = texts[0];

            List<string> lines = new List<string>();
            for (int i = 1; i < texts.Length; i++)
            {
                lines.Add(texts[i]);
            }
            rhinoAssemblyLocations = lines.ToArray();
        }

        public bool RunAssembly(IExecutorInfo assemblyRunner, out string runError, out string runTime)
        {
            if (assemblyRunner == null)
            {
                runError = "assemblyRunner is null";
                runTime = "";
                return false;
            }
            if (m_OnRunning)
            {
                runError = "cannot run while another command is running";
                runTime = "";
                return false;
            }
            ReaderWriterLock.AcquireWriterLock(-1);
            m_OnRunning = true;
            try
            {
                if (!TestConnected())
                {
                    runError = "Aimo Rhino is Stopped";
                    runTime = "";
                    return false;
                }
                string IsCN = assemblyRunner.LangInCN ? "CN" : "EN";
                List<string> allAssemblies = new List<string>() { assemblyRunner.AssemblyPath };
                allAssemblies.AddRange(assemblyRunner.ExternalAssembliesPaths);
                string contents = SendAndReceiveLoop(assemblyRunner, $"Run-{IsCN}-{Join(allAssemblies)}");
                string[] values = Split(contents);
                bool result = bool.Parse(values[0]);
                runTime = values[1];
                runError = values[2];
                return result;
            }
            catch (Exception ex)
            {
                runError = $"Connection Error:\r\n   {ex.Message}";
                runTime = "";
                return false;
            }
            finally
            {
                m_OnRunning = false;
                ReaderWriterLock.ReleaseWriterLock();
            }
        }
        string Join(IEnumerable<string> separator)
        {
            return string.Join(
                PipeUtils.Spliter, separator
                .Select(x => x.Replace("\r\n", PipeUtils.ReturnFlag))
                );
        }
        string[] Split(string contents)
        {
            return contents.Split(new string[] { PipeUtils.Spliter }, StringSplitOptions.None).Select(x => x.Replace(PipeUtils.ReturnFlag, "\r\n")).ToArray();
        }

        public Bitmap? GetScreenShot(Size size)
        {
            if (m_OnRunning) return null;
            ReaderWriterLock.AcquireWriterLock(-1);
            m_OnRunning = true;
            try
            {
                if (!TestConnected())
                {
                    return null;
                }
                string? content = SendAndReceive($"ScreenShot-{size.Width}-{size.Height}");
                if (string.IsNullOrEmpty(content))
                {
                    return null;
                }

                byte[] bytes = Convert.FromBase64String(content);
                MemoryStream stream = new MemoryStream(bytes, false);
                Bitmap bitmap = new Bitmap(Image.FromStream(stream));
                stream.Close();
                stream.Dispose();
                return bitmap;
            }
            catch
            {
                return null;
            }
            finally
            {
                m_OnRunning = false;
                ReaderWriterLock.ReleaseWriterLock();
            }
        }

        string SendAndReceiveLoop(IExecutorInfo assemblyRunner, string message)
        {
            Writer.WriteLine(message);
            Client.WaitForPipeDrain();
            Writer.Flush();
            while (true)
            {
                string line = Reader.ReadLine() ?? throw new Exception("Connection to Rhino is broken");
                //MessageBox.Show(line);
                if (line.StartsWith(PipeUtils.EndFlag))
                {
                    return line[(PipeUtils.EndFlag.Length + PipeUtils.Spliter.Length)..];
                }
                else if (line.StartsWith(PipeUtils.PrintFlag))
                {
                    line = line[PipeUtils.PrintFlag.Length..];
                    assemblyRunner?.Printer.PrintDebug(Encoding.UTF8.GetString(Convert.FromBase64String(line)));
                }
                else
                {
                    assemblyRunner?.Printer.PrintDebug($"Reveived unknown message: {line}");
                }
            }
        }

        bool TestConnected()
        {
            try
            {
                Writer.WriteLine("Test Connected");
                Client.WaitForPipeDrain();
                Writer.Flush();
                string? line = Reader.ReadLine();
                if (line != "Received")
                {
                    return false;
                }
                return Client.IsConnected;
            }
            catch
            {

            }
            return false;
        }

        string? SendAndReceive(string message)
        {
            Writer.WriteLine(message);
            Client.WaitForPipeDrain();
            Writer.Flush();
            return Reader.ReadLine();
        }
        public void Dispose()
        {
            Client.Close();
            Client.Dispose();
        }
    }
}
